package com.face.server.dao;

import com.face.server.entity.*;
import com.face.server.service.UtileService;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import static org.springframework.data.mongodb.core.query.Criteria.*;

import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.validation.constraints.NotNull;

@Repository
public class JoinedDAO {

    @Autowired
    MongoTemplate mongoTemplate;

    public Student findByUserNameAndType(String username) {
        return mongoTemplate.findOne(Query.query(
                where("auth.username").is(username)
                .and("type").is(0)
        ), Student.class, "joined");
    }

    public Student findOne(String id) {
        return mongoTemplate.findById(id, Student.class, "joined");
    }

    public int updateProject(Student student) {
        return mongoTemplate.updateFirst(Query.query(
                where("_id").is(student.getId())
        ), new Update()
                .set("name", student.getName())
                .set("phone", student.getPhone()),
                "joined"
        ).getN();
    }

    public int updateProPhoto(Student student) {
        return mongoTemplate.updateFirst(Query.query(
                where("_id").is(student.getId())
                ), new Update()
                        .set("auth.proPhoto", student.getAuth().getProPhoto()),
                "joined"
        ).getN();
    }



    public List<Student> listStuWithDorm(int buildNum, int roomNum) {
        return mongoTemplate.find(Query.query(
                where("type").is(0).and("dormitoryBuildNum").is(buildNum).and("dormitoryNum").is(roomNum)
            ) ,Student.class,"joined");
    }

    public Long countByDorm() {
        return mongoTemplate.count(Query.query(
                where("type").is(0).and("dormitoryNum").exists(true)),
                Student.class, "joined"
        );
    }

    public int removeFaceModel(String id,@NotNull FaceModel faceModel) {
        return faceModel == null ? 0 : mongoTemplate.updateFirst(Query.query(where("_id").is(id)),
                new Update().pull("faceModels",
                        Query.query(where("$id").is(new ObjectId(faceModel.getId())))), Joined.class).getN();

    }

    public List<Student> pageStudents(Pageable pageRequest) {
        int page=pageRequest.getPageNumber()>0?pageRequest.getPageNumber()*pageRequest.getPageSize():0;
        Aggregation aggregation=Aggregation.newAggregation(
                Aggregation.unwind("dormitoryNumList"),
                Aggregation.match(Criteria.where("type").is(0)),
                Aggregation.skip((long)page),
                Aggregation.limit(pageRequest.getPageSize())
        );
        if (pageRequest.getSort()!=null){
            aggregation=Aggregation.newAggregation(
                    Aggregation.unwind("dormitoryNumList"),
                    Aggregation.match(Criteria.where("type").is(0)),
                    Aggregation.skip((long)page),
                    Aggregation.sort(pageRequest.getSort()),
                    Aggregation.limit(pageRequest.getPageSize())
            );
        }
        return mongoTemplate.aggregate(aggregation,"joined",Student.class).getMappedResults();
    }

    public List<Student> pageStudents(Pageable pageRequest, String key1, String key2) {
        String[] keys1=new String[]{"name","studentNumber"};
        String[] keys2=new String[]{"dormitoryBuildNum","dormitoryNum","sex"};

        Criteria integ=new Criteria();
        ArrayList<Criteria> temp = new ArrayList<>();

        Criteria criteria=new Criteria();
        List<Criteria> criteriaList=new ArrayList<Criteria>();
        Criteria criteria1=new Criteria();
        if (key1!=null)
            criteria1.orOperator(Arrays.stream(keys1).map(e->Criteria.where(e).regex(key1)).toArray(s->new Criteria[s]));
        Criteria criteria2=new Criteria();
        if (key1!=null&& UtileService.isNum(key1))
            criteria2.orOperator(Arrays.stream(keys2).map(e->Criteria.where(e).is(Integer.parseInt(key1))).toArray(s->new Criteria[s]));
        if (key1!=null&& UtileService.isBool(key1))
            criteria2.orOperator(Arrays.stream(keys2).map(e->Criteria.where(e).is(Boolean.parseBoolean(key1))).toArray(s->new Criteria[s]));
        criteriaList.add(criteria1);criteriaList.add(criteria2);
        criteria.orOperator(criteriaList.stream().toArray(s->new Criteria[s]));
        temp.add(criteria);

        if (key2!=null&&key2.equals("")==false){
            List<Criteria> criList=new ArrayList<Criteria>();
            Criteria cri=new Criteria();
            Criteria cri1=new Criteria();
            cri1.orOperator(Arrays.stream(keys1).map(e->Criteria.where(e).regex(key2)).toArray(s->new Criteria[s]));
            Criteria cri2=new Criteria();
            if (UtileService.isNum(key2)==true)
                cri2.orOperator(Arrays.stream(keys2).map(e->Criteria.where(e).is(Integer.parseInt(key2))).toArray(s->new Criteria[s]));
            if (UtileService.isBool(key2)==true)
                cri2.orOperator(Arrays.stream(keys2).map(e->Criteria.where(e).is(Boolean.parseBoolean(key2))).toArray(s->new Criteria[s]));
            criList.add(cri1);
            criList.add(cri2);
            cri.orOperator(criList.stream().toArray(s->new Criteria[s]));
            temp.add(cri);
        }
        integ.andOperator(temp.stream().toArray(s->new Criteria[s]));

        int page=pageRequest.getPageNumber()>0?pageRequest.getPageNumber()*pageRequest.getPageSize():0;
        Aggregation aggregation=Aggregation.newAggregation(
                Aggregation.match(new Criteria().andOperator(integ,new Criteria().where("type").is(0))),
                Aggregation.skip((long)page),
                Aggregation.limit(pageRequest.getPageSize())
        );
        if (pageRequest.getSort()!=null){
            aggregation=Aggregation.newAggregation(
                    Aggregation.match(new Criteria().andOperator(integ,new Criteria().where("type").is(0))),
                    Aggregation.sort(pageRequest.getSort()),
                    Aggregation.skip((long)page),
                    Aggregation.limit(pageRequest.getPageSize())
            );
        }
        AggregationResults<Student> aggrResult = mongoTemplate.aggregate(aggregation, "joined", Student.class);
        return aggrResult.getMappedResults();
    }

    public List<Student> pageMyStudents(List<Integer> dormitoryNumList, Pageable pageRequest) {
        int page=pageRequest.getPageNumber()>0?pageRequest.getPageNumber()*pageRequest.getPageSize():0;
        Aggregation aggregation=Aggregation.newAggregation(
                Aggregation.unwind("dormitoryNumList"),
                Aggregation.match(Criteria.where("type").is(0).and("dormitoryBuildNum").in(dormitoryNumList)),
                Aggregation.skip((long)page),
                Aggregation.limit(pageRequest.getPageSize())
        );
        if (pageRequest.getSort()!=null){
            aggregation=Aggregation.newAggregation(
                    Aggregation.unwind("dormitoryNumList"),
                    Aggregation.match(Criteria.where("type").is(0).and("dormitoryBuildNum").in(dormitoryNumList)),
                    Aggregation.skip((long)page),
                    Aggregation.sort(pageRequest.getSort()),
                    Aggregation.limit(pageRequest.getPageSize())
            );
        }
        return mongoTemplate.aggregate(aggregation,"joined",Student.class).getMappedResults();
    }


    public List<Student> pageMyStudents(Pageable pageRequest, String key1, String key2, List<Integer> dormitoryNumList) {
        String[] keys1=new String[]{"name","studentNumber"};
        String[] keys2=new String[]{"dormitoryBuildNum","dormitoryNum","sex"};

        Criteria c=new Criteria();
        c.and("dormitoryBuildNum").in(dormitoryNumList);

        Criteria integ=new Criteria();
        ArrayList<Criteria> temp = new ArrayList<>();
        temp.add(c);

        Criteria criteria=new Criteria();
        List<Criteria> criteriaList=new ArrayList<Criteria>();
        Criteria criteria1=new Criteria();
        if (key1!=null)
            criteria1.orOperator(Arrays.stream(keys1).map(e->Criteria.where(e).regex(key1)).toArray(s->new Criteria[s]));
        Criteria criteria2=new Criteria();
        if (key1!=null&& UtileService.isNum(key1))
            criteria2.orOperator(Arrays.stream(keys2).map(e->Criteria.where(e).is(Integer.parseInt(key1))).toArray(s->new Criteria[s]));
        if (key1!=null&& UtileService.isBool(key1))
            criteria2.orOperator(Arrays.stream(keys2).map(e->Criteria.where(e).is(Boolean.parseBoolean(key1))).toArray(s->new Criteria[s]));
        criteriaList.add(criteria1);criteriaList.add(criteria2);
        criteria.orOperator(criteriaList.stream().toArray(s->new Criteria[s]));
        temp.add(criteria);

        if (key2!=null&&key2.equals("")==false){
            List<Criteria> criList=new ArrayList<Criteria>();
            Criteria cri=new Criteria();
            Criteria cri1=new Criteria();
            cri1.orOperator(Arrays.stream(keys1).map(e->Criteria.where(e).regex(key2)).toArray(s->new Criteria[s]));
            Criteria cri2=new Criteria();
            if (UtileService.isNum(key2)==true)
                cri2.orOperator(Arrays.stream(keys2).map(e->Criteria.where(e).is(Integer.parseInt(key2))).toArray(s->new Criteria[s]));
            if (UtileService.isBool(key2)==true)
                cri2.orOperator(Arrays.stream(keys2).map(e->Criteria.where(e).is(Boolean.parseBoolean(key2))).toArray(s->new Criteria[s]));
            criList.add(cri1);
            criList.add(cri2);
            cri.orOperator(criList.stream().toArray(s->new Criteria[s]));
            temp.add(cri);
        }
        integ.andOperator(temp.stream().toArray(s->new Criteria[s]));

        int page=pageRequest.getPageNumber()>0?pageRequest.getPageNumber()*pageRequest.getPageSize():0;
        Aggregation aggregation=Aggregation.newAggregation(
                Aggregation.match(new Criteria().andOperator(integ,new Criteria().where("type").is(0))),
                Aggregation.skip((long)page),
                Aggregation.limit(pageRequest.getPageSize())
        );
        if (pageRequest.getSort()!=null){
            aggregation=Aggregation.newAggregation(
                    Aggregation.match(new Criteria().andOperator(integ,new Criteria().where("type").is(0))),
                    Aggregation.sort(pageRequest.getSort()),
                    Aggregation.skip((long)page),
                    Aggregation.limit(pageRequest.getPageSize())
            );
        }
        AggregationResults<Student> aggrResult = mongoTemplate.aggregate(aggregation, "joined", Student.class);
        return aggrResult.getMappedResults();
    }

    public void createStudent(Student student) {
         mongoTemplate.insert(student,"joined");
    }

    public List<Visitor> listVisitors(String id, Date now,Pageable pageable) {
        Aggregation aggregation=Aggregation.newAggregation(
                Aggregation.match(new Criteria("student.$id").is(new ObjectId(id))
                        .and("effectDate").lte(now)
                        .and("endDate").gte(now)
                        .and("type").is(1)),
                Aggregation.skip((long)pageable.getOffset()),
                Aggregation.limit(pageable.getPageSize())
        );
        return mongoTemplate.aggregate(aggregation,"joined",Visitor.class).getMappedResults();
    }

    public List<Student> findOutsideEndDate(Date now, int type) {
        return mongoTemplate.find(Query.query(
                where("type").is(0).and("endDate").lte(now)
        ),Student.class,"joined");
    }

    public int deleteOutsideEndDate(Date now, int type) {
        return mongoTemplate.remove(Query.query(
                where("type").is(0).and("endDate").lte(now)
        ),Student.class,"joined").getN();
    }

    public Visitor findByNameAndType(String name, int type) {
        return mongoTemplate.findOne(Query.query(
                where("name").is(name).and("type").is(type)
        ),Visitor.class,"joined");
    }

    public DormitoryAdmin findDormAdminByName(String name) {
        return mongoTemplate.findOne(Query.query(
                where("name").is(name).and("type").is(2)
        ),DormitoryAdmin.class,"joined");
    }

}
